﻿using System;
using System.Collections.Generic;
using System.Linq;
using NHibernate.Linq;
using Xenta.Api;
using Xenta.Entities;
using Xenta.Enums;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;

namespace Xenta.Operations
{
    #region __General__

    /// <summary>
    /// Creates a sales item.
    /// </summary>
    public class CreateSalesItem : CreateAuditable<SalesItemEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public CreateSalesItem()
        {
            AvailableFrom = DateTime.UtcNow;
            AvailableTo = DateTime.UtcNow.AddMonths(1);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        return seller.EntityID == SellerID;
                    });
            }
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item identifier.
        /// </summary>
        public int CatalogItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the SKU.
        /// </summary>
        public string SKU
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public decimal Price
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SalesItemFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the availability date 'from'.
        /// </summary>
        public DateTime AvailableFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the availability date 'to'.
        /// </summary>
        public DateTime AvailableTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(SKU))
                return Err.Of<SalesItemEntity>().NotValid("SKU");
            if(Price < Decimal.Zero)
                return Err.Of<SalesItemEntity>().NotValid("Price");
            return base.Validate(uow);
        }

        protected override void Construct(IUnitOfWork uow, SalesItemEntity entity)
        {
            base.Construct(uow, entity);

            entity.Seller = uow.Execute(new GetSeller
            {
                ThrowsException = true,
                EntityID = SellerID
            }).Result;
            entity.CatalogItem = uow.Execute(new GetCatalogItem
            {
                ThrowsException = true,
                EntityID = CatalogItemID
            }).Result;
            entity.SKU = SKU.TrimToLen(100);
            entity.Price = Price;
            entity.Flags = Flags;
            entity.AvailableFrom = AvailableFrom;
            entity.AvailableTo = AvailableTo;
        }

        #endregion
    }

    /// <summary>
    /// Updates the sales item.
    /// </summary>
    public class UpdateSalesItem : UpdateAuditable<SalesItemEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var item = uow.Execute(new GetSalesItem
                        {
                            EntityID = EntityID
                        }).Result;
                        if(item == null)
                            return false;
                        return item.Seller.Equals(seller);
                    });
            }
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item identifier.
        /// </summary>
        public int CatalogItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the SKU.
        /// </summary>
        public string SKU
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price.
        /// </summary>
        public decimal Price
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        public SalesItemFlags Flags
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the availability date 'from'.
        /// </summary>
        public DateTime AvailableFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the availability date 'to'.
        /// </summary>
        public DateTime AvailableTo
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(String.IsNullOrWhiteSpace(SKU))
                return Err.Of<SalesItemEntity>().NotValid("SKU");
            if(Price < Decimal.Zero)
                return Err.Of<SalesItemEntity>().NotValid("Price");
            return base.Validate(uow);
        }

        protected override void Revise(IUnitOfWork uow, SalesItemEntity entity)
        {
            base.Revise(uow, entity);

            entity.Seller = uow.Execute(new GetSeller
            {
                ThrowsException = true,
                EntityID = SellerID
            }).Result;
            entity.CatalogItem = uow.Execute(new GetCatalogItem
            {
                ThrowsException = true,
                EntityID = CatalogItemID
            }).Result;
            entity.SKU = SKU.TrimToLen(100);
            entity.Price = Price;
            entity.Flags = Flags;
            entity.AvailableFrom = AvailableFrom;
            entity.AvailableTo = AvailableTo;
        }

        #endregion
    }

    /// <summary>
    /// Deletes the sales item.
    /// </summary>
    public class DeleteSalesItem : DeleteEntity<SalesItemEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var item = uow.Execute(new GetSalesItem
                        {
                            EntityID = EntityID
                        }).Result;
                        if(item == null)
                            return false;
                        return item.Seller.Equals(seller);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override void Destruct(IUnitOfWork uow, SalesItemEntity entity)
        {
            foreach(var i in entity.Attributes)
            {
                uow.Execute(new DeleteSalesItemAttribute
                {
                    EntityID = i.EntityID
                });
            }
            foreach(var i in entity.Options)
            {
                uow.Execute(new UnmapSalesOptionFromSalesItem
                {
                    EntityID = entity.EntityID,
                    ChildID = i.EntityID
                });
            }
            foreach(var i in entity.StockEntries)
            {
                uow.Execute(new DeleteStockEntry
                {
                    EntityID = i.EntityID
                });
            }
            base.Destruct(uow, entity);
        }

        #endregion
    }

    /// <summary>
    /// Gets the sales item by identifier.
    /// </summary>
    public class GetSalesItem : GetEntity<SalesItemEntity>,
        IApiOperation
    {
    }

    /// <summary>
    /// Gets the sales item by SKU.
    /// </summary>
    public class GetSalesItemBySKU : QueryEntity<SalesItemEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets or sets the SKU.
        /// </summary>
        public string SKU
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesItemEntity> Build(IUnitOfWork uow, IQueryable<SalesItemEntity> query)
        {
            string sku = StringHelper.EnsureNotNull(SKU).TrimToLen(100);
            return base.Build(uow, query).Where(x => x.SKU.Equals(sku));
        }

        #endregion
    }

    #endregion

    #region __Search__

    /// <summary>
    /// Search over sales item collection.
    /// </summary>
    public class SearchSalesItems : SearchOperation<SalesItemEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchSalesItems()
        {
            Term = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SortBy = new []
            {
                SalesItemSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int? SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price "from".
        /// </summary>
        public decimal? PriceFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price "to".
        /// </summary>
        public decimal? PriceTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public SalesItemFlags FlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the required availability date.
        /// </summary>
        public DateTime? AvailableOn
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort by.
        /// </summary>
        public SalesItemSortBy[] SortBy
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesItemEntity> Build(IUnitOfWork uow, IQueryable<SalesItemEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.CatalogItem)
                .ThenFetch(x => x.Supplier)
                .Fetch(x => x.Seller)
                .ThenFetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            query = query.FilterByNumRange(x => x.Price, PriceFrom, PriceTo);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.SKU.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.SKU.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.SKU.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.SKU.Equals(Term.Keyword));
                    break;
            }
            if(SellerID.HasValue)
                query = query.Where(x => x.Seller.EntityID == SellerID.Value);
            if(AvailableOn.HasValue)
                query = query.Where(x => x.AvailableFrom <= AvailableOn.Value && x.AvailableTo >= AvailableOn.Value);
            if(SortBy != null)
            {
                for(int i = 0; i < SortBy.Length; i++)
                {
                    switch(SortBy[i])
                    {
                        case SalesItemSortBy.SKU:
                            query = query.OrderBy(x => x.SKU, SortDir, i != 0);
                            break;
                        case SalesItemSortBy.Price:
                            query = query.OrderBy(x => x.Price, SortDir, i != 0);
                            break;
                        case SalesItemSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, i != 0);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, i != 0);
                            break;
                    }
                }
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Search over sales item collection.
    /// </summary>
    public class SearchSalesItemsOverCatalog : SearchOperation<SalesItemEntity>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public SearchSalesItemsOverCatalog()
        {
            Term = SearchTerm.Empty;
            SortDir = SortDirection.Ascending;
            SalesSortBy = new[]
            {
                SalesItemSortBy.ID
            };
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the language identifier.
        /// </summary>
        public int? LanguageID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the seller identifier.
        /// </summary>
        public int? SellerID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the supplier identifier.
        /// </summary>
        public int? SupplierID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the category identifier.
        /// </summary>
        public int? CategoryID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the group identifier.
        /// </summary>
        public int? GroupID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the attribute identifiers.
        /// </summary>
        public int[] AttributeIDs
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the tag identifier.
        /// </summary>
        public int? TagID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item identifier.
        /// </summary>
        public int? ItemID
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price "from".
        /// </summary>
        public decimal? PriceFrom
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the price "to".
        /// </summary>
        public decimal? PriceTo
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales item flag mask.
        /// </summary>
        public SalesItemFlags SalesFlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item flag mask.
        /// </summary>
        public CatalogItemFlags CatalogFlagMask
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the required availability date.
        /// </summary>
        public DateTime? AvailableOn
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sales item sort by.
        /// </summary>
        public SalesItemSortBy[] SalesSortBy
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the catalog item sort by.
        /// </summary>
        public CatalogItemSortBy[] CatalogSortBy
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the sort direction.
        /// </summary>
        public SortDirection SortDir
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesItemEntity> Build(IUnitOfWork uow, IQueryable<SalesItemEntity> query)
        {
            query = base.Build(uow, query);
            query = query.Fetch(x => x.CatalogItem)
                .ThenFetch(x => x.Supplier)
                .Fetch(x => x.Seller)
                .ThenFetch(x => x.Account);
            query = query.FilterByFlags(x => (int)x.Flags, (int)SalesFlagMask);
            query = query.FilterByFlags(x => (int)x.CatalogItem.Flags, (int)CatalogFlagMask);
            query = query.FilterByNumRange(x => x.Price, PriceFrom, PriceTo);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    if(LanguageID.HasValue)
                        query = query.Where(x => x.SKU.StartsWith(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Language.EntityID == LanguageID.Value &&
                            i.Name.StartsWith(Term.Keyword)));
                    else
                        query = query.Where(x => x.SKU.StartsWith(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Name.StartsWith(Term.Keyword)));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    if(LanguageID.HasValue)
                        query = query.Where(x => x.SKU.EndsWith(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Language.EntityID == LanguageID.Value &&
                            i.Name.EndsWith(Term.Keyword)));
                    else
                        query = query.Where(x => x.SKU.EndsWith(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Name.EndsWith(Term.Keyword)));
                    break;
                case SearchTerm.SearchMode.Contains:
                    if(LanguageID.HasValue)
                        query = query.Where(x => x.SKU.Contains(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Language.EntityID == LanguageID.Value &&
                            (i.Name.Contains(Term.Keyword) ||
                            i.ShortDescription.Contains(Term.Keyword))));
                    else
                        query = query.Where(x => x.SKU.Contains(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Name.Contains(Term.Keyword) ||
                            i.ShortDescription.Contains(Term.Keyword)));
                    break;
                case SearchTerm.SearchMode.Equals:
                    if(LanguageID.HasValue)
                        query = query.Where(x => x.SKU.Equals(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Language.EntityID == LanguageID.Value &&
                            i.Name.Equals(Term.Keyword)));
                    else
                        query = query.Where(x => x.SKU.Equals(Term.Keyword) || x.CatalogItem.Translations.Any(i =>
                            i.Name.Equals(Term.Keyword)));
                    break;
                default:
                    if(LanguageID.HasValue)
                        query = query.Where(x => x.CatalogItem.Translations.Any(i => i.Language.EntityID == LanguageID.Value));
                    break;
            }
            if(SellerID.HasValue)
                query = query.Where(x => x.Seller.EntityID == SellerID.Value);
            if(SupplierID.HasValue)
                query = query.Where(x => x.CatalogItem.Supplier.EntityID == SupplierID.Value);
            if(ItemID.HasValue)
                query = query.Where(x => x.CatalogItem.EntityID == ItemID.Value);
            if(CategoryID.HasValue)
            {
                query = CategoryID.Value != 0
                    ? query.Where(x => x.CatalogItem.Categories.Any(i => i.EntityID == CategoryID.Value))
                    : query.Where(x => !x.CatalogItem.Categories.Any());
            }
            if(TagID.HasValue)
                query = query.Where(x => x.CatalogItem.Tags.Any(i => i.EntityID == TagID.Value));
            if(AttributeIDs != null && AttributeIDs.Any())
                query = query.Where(x => x.CatalogItem.Attributes.Count(i => AttributeIDs.Contains(i.EntityID)) == AttributeIDs.Count());
            if(GroupID.HasValue)
            {
                query = GroupID.Value != 0
                    ? query.Where(x => x.CatalogItem.Groups.Any(i => i.EntityID == GroupID.Value))
                    : query.Where(x => !x.CatalogItem.Groups.Any());
            }
            if(AvailableOn.HasValue)
                query = query.Where(x => x.AvailableFrom <= AvailableOn.Value && x.AvailableTo >= AvailableOn.Value);
            bool isSubsequent = false;
            if(SalesSortBy != null)
            {
                foreach(var i in SalesSortBy)
                {
                    switch(i)
                    {
                        case SalesItemSortBy.SKU:
                            query = query.OrderBy(x => x.SKU, SortDir, isSubsequent);
                            break;
                        case SalesItemSortBy.Price:
                            query = query.OrderBy(x => x.Price, SortDir, isSubsequent);
                            break;
                        case SalesItemSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CreatedOn, SortDir, isSubsequent);
                            break;
                        default:
                            query = query.OrderBy(x => x.EntityID, SortDir, isSubsequent);
                            break;
                    }
                    isSubsequent = true;
                }
            }
            if(CatalogSortBy != null)
            {
                foreach(var i in CatalogSortBy)
                {
                    switch(i)
                    {
                        case CatalogItemSortBy.CommentCount:
                            query = query.OrderBy(x => x.CatalogItem.CommentCount, SortDir, isSubsequent);
                            break;
                        case CatalogItemSortBy.RatingCount:
                            query = query.OrderBy(x => x.CatalogItem.RatingCount, SortDir, isSubsequent);
                            break;
                        case CatalogItemSortBy.RatingAverage:
                            query = query.OrderBy(x => x.CatalogItem.RatingAverage, SortDir, isSubsequent);
                            break;
                        case CatalogItemSortBy.CreatedOn:
                            query = query.OrderBy(x => x.CatalogItem.CreatedOn, SortDir, isSubsequent);
                            break;
                        default:
                            query = query.OrderBy(x => x.CatalogItem.EntityID, SortDir, isSubsequent);
                            break;
                    }
                    isSubsequent = true;
                }
            }
            return query;
        }

        #endregion
    }

    /// <summary>
    /// Gets the collection of items as a dictionary, where 
    /// key - item identifier and value - item SKU.
    /// </summary>
    public class GetSalesItemDictionary : SearchOperation<SalesItemEntity, Int32, String>,
        IApiOperation
    {
        #region Ctors

        /// <summary>
        /// Initializes a new class instance.
        /// </summary>
        public GetSalesItemDictionary()
        {
            Term = SearchTerm.Empty;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the search term.
        /// </summary>
        public SearchTerm Term
        {
            get;
            set;
        }

        /// <summary>
        /// Gets or sets the flag mask.
        /// </summary>
        public SalesItemFlags FlagMask
        {
            get;
            set;
        }

        #endregion

        #region Utilities

        protected override IQueryable<SalesItemEntity> Build(IUnitOfWork uow, IQueryable<SalesItemEntity> query)
        {
            query = base.Build(uow, query);
            query = query.FilterByFlags(x => (int)x.Flags, (int)FlagMask);
            switch(Term.Mode)
            {
                case SearchTerm.SearchMode.StartsWith:
                    query = query.Where(x => x.SKU.StartsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.EndsWith:
                    query = query.Where(x => x.SKU.EndsWith(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Contains:
                    query = query.Where(x => x.SKU.Contains(Term.Keyword));
                    break;
                case SearchTerm.SearchMode.Equals:
                    query = query.Where(x => x.SKU.Equals(Term.Keyword));
                    break;
            }
            query = query.OrderBy(x => x.SKU);
            return query;
        }

        protected override IQueryable<KeyValuePair<Int32, String>> Map(IQueryable<SalesItemEntity> query)
        {
            return query.Select(x => new KeyValuePair<Int32, String>(x.EntityID, x.SKU));
        }

        #endregion
    }

    #endregion

    #region __Map__

    /// <summary>
    /// Maps the option to item.
    /// </summary>
    public class MapSalesOptionToSalesItem : MapOperation
        <SalesItemEntity, SalesOptionEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var item = uow.Execute(new GetSalesItem
                        {
                            EntityID = EntityID
                        }).Result;
                        if(item == null)
                            return false;
                        return item.Seller.Equals(seller);
                    });
            }
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(uow.Execute(new GetSalesOption
            {
                ThrowsException = true,
                EntityID = ChildID
            }).Result.Flags.HasFlag(SalesOptionFlags.Order))
            {
                return Err.Of(this).NotAllowed();
            }
            return base.Validate(uow);
        }

        #endregion
    }

    /// <summary>
    /// Unmaps the option from item.
    /// </summary>
    public class UnmapSalesOptionFromSalesItem : UnmapOperation
        <SalesItemEntity, SalesOptionEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .IsVerified()
                    .Rule((ctx, uow) =>
                    {
                        var seller = ctx.Seller(uow);
                        if(seller == null || !seller.Flags.HasFlag(SellerFlags.Active))
                            return false;
                        var item = uow.Execute(new GetSalesItem
                        {
                            EntityID = EntityID
                        }).Result;
                        if(item == null)
                            return false;
                        return item.Seller.Equals(seller);
                    });
            }
        }

        #endregion
    }

    /// <summary>
    /// Maps the rule to item.
    /// </summary>
    public class MapSalesRuleToSalesItem : MapOperation
        <SalesItemEntity, SalesRuleEntity>,
        ISecuredOperation, IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion

        #region Utilities

        protected override Err Validate(IUnitOfWork uow)
        {
            if(uow.Execute(new GetSalesRule
            {
                ThrowsException = true,
                EntityID = ChildID
            }).Result.CommonFlags.HasFlag(SalesRuleFlags.OrderScope))
            {
                return Err.Of(this).NotAllowed();
            }
            return base.Validate(uow);
        }

        #endregion
    }

    /// <summary>
    /// Unmaps the rule from item.
    /// </summary>
    public class UnmapSalesRuleFromSalesItem : UnmapOperation
        <SalesItemEntity, SalesRuleEntity>,
        IApiOperation
    {
        #region Properties

        /// <summary>
        /// Gets the collection of security policies.
        /// </summary>
        public IEnumerable<SecurityPolicy> SecurityPolicies
        {
            get
            {
                yield return new SecurityPolicy()
                    .IsAuthenticated()
                    .HasPermission("SALES");
            }
        }

        #endregion
    }

    #endregion
}
